{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.core.display import display, HTML\n",
    "display(HTML(\"<style>.container { width:100% !important; }</style>\"))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Data Structures and Sequences"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Python’s data structures are simple, but powerful. \n",
    "\n",
    "Mastering their use is a critical part of becoming a proficient Python programmer and data analyst."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tuple"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A tuple is a one-dimensional, fixed-length, **immutable** sequence of Python objects. The\n",
    "easiest way to create one is with a comma-separated sequence of values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tup = 4, 5, 6\n",
    "tup"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When defining tuples in more complicated expressions, it’s often necessary to enclose\n",
    "the values in parentheses, as in this example of creating a tuple of tuples:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nested_tup = (4, 5, 6), (7, 8)\n",
    "nested_tup"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Any sequence or iterator can be converted to a tuple by invoking tuple:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tuple([4, 0, 2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tup = tuple('string')\n",
    "tup"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Elements can be accessed with square brackets [] as with most other sequence types.\n",
    "Like C, C++, Java, and many other languages, sequences are 0-indexed in Python:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tup[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise:\n",
    "\n",
    "Given the tuple below, write code that alters the third elements to False."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tup = tuple(['foo', [1, 2], True])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "While the objects stored in a tuple may be mutable themselves, once created it’s not\n",
    "possible to modify which object is stored in each slot:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tup[1].append(3)\n",
    "tup"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tuples can be concatenated using the + operator to produce longer tuples:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "(4, None, 'foo') + (6, 0) + ('bar',)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Multiplying a tuple by an integer, as with lists, has the effect of concatenating together\n",
    "that many copies of the tuple."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "('foo', 'bar') * 4"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that the objects themselves are not copied, only the references to them."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Unpacking tuples**\n",
    "\n",
    "If you try to assign to a tuple-like expression of variables, Python will attempt to unpack\n",
    "the value on the right-hand side of the equals sign:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tup = (4, 5, 6)\n",
    "a, b, c = tup\n",
    "b\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Even sequences with nested tuples can be unpacked:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tup = 4, 5, (6, 7)\n",
    "a, b, (c, d) = tup\n",
    "d"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ordinarily, swapping values between two variables is a task which in many languages\n",
    "might look like:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tmp = a\n",
    "a = b\n",
    "b = tmp"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise:\n",
    "\n",
    "Write code, using tuples that swaps the values between _a_ and *b*, without using a temporary variable:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = 2\n",
    "b = 3\n",
    "\n",
    "# YOUR CODE HERE:\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One of the most common uses of variable unpacking when iterating over sequences of\n",
    "tuples or lists:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]\n",
    "for a, b, c in seq:\n",
    "    print (a, b, c)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Another common use is for returning multiple values from a function. More on this\n",
    "later.\n",
    "\n",
    "**Tuple methods**\n",
    "\n",
    "Since the size and contents of a tuple cannot be modified, it is very light on instance\n",
    "methods. One particularly useful one (also available on lists) is count, which counts the\n",
    "number of occurrences of a value:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = (1, 2, 2, 2, 3, 4, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a.count(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# List"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In contrast with tuples, lists are variable-length and their contents can be modified.\n",
    "They can be defined using square brackets [] or using the list type function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a_list = [2, 3, 7, None]\n",
    "tup = ('foo', 'bar', 'baz')\n",
    "b_list = list(tup)\n",
    "b_list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b_list[1] = 'peekaboo'\n",
    "b_list"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lists and tuples are semantically similar as one-dimensional sequences of objects and\n",
    "thus can be used interchangeably in many functions.\n",
    "\n",
    "**Adding and removing elements**\n",
    "\n",
    "Elements can be appended to the end of the list with the append method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b_list.append('dwarf')\n",
    "b_list"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using insert you can insert an element at a specific location in the list:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b_list.insert(1, 'red')\n",
    "b_list"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "insert is computationally expensive compared with append as references\n",
    "to subsequent elements have to be shifted internally to make room for\n",
    "the new element."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The inverse operation to insert is pop, which removes and returns an element at a\n",
    "particular index:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b_list.pop(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b_list"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Elements can be removed by value using remove, which locates the first such value and\n",
    "removes it from the list:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b_list.append('foo')\n",
    "b_list\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b_list.remove('foo')\n",
    "b_list"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If performance is not a concern, by using append and remove, a Python list can be used\n",
    "as a perfectly suitable “multi-set” data structure.\n",
    "\n",
    "You can check if a list contains a value using the in keyword:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "'dwarf' in b_list"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that checking whether a list contains a value is a lot slower than dicts and sets as\n",
    "Python makes a linear scan across the values of the list, whereas the others (based on\n",
    "hash tables) can make the check in constant time.\n",
    "\n",
    "**Concatenating and combining lists**\n",
    "\n",
    "Similar to tuples, adding two lists together with + concatenates them:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "[4, None, 'foo'] + [7, 8, (2, 3)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you have a list already defined, you can append multiple elements to it using the\n",
    "extend method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = [4, None, 'foo']\n",
    "x.extend([7, 8, (2, 3)])\n",
    "x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that list concatenation is a comparatively expensive operation since a new list must\n",
    "be created and the objects copied over. Using extend to append elements to an existing\n",
    "list, especially if you are building up a large list, is usually preferable. Thus,"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "everything = []\n",
    "for chunk in list_of_lists:\n",
    "    everything.extend(chunk)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "is faster than than the concatenative alternative"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "everything = []\n",
    "for chunk in list_of_lists:\n",
    "    everything = everything + chunk"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Sorting**\n",
    "\n",
    "A list can be sorted in-place (without creating a new object) by calling its sort function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = [7, 2, 5, 1, 3]\n",
    "a.sort()\n",
    "a"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "sort has a few options that will occasionally come in handy. One is the ability to pass\n",
    "a secondary sort key, i.e. a function that produces a value to use to sort the objects. For\n",
    "example, we could sort a collection of strings by their lengths:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "b = ['saw', 'small', 'He', 'foxes', 'six']\n",
    "b.sort(key=len)\n",
    "b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise:\n",
    "\n",
    "Write a function that returns the smallest number from a list."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = [7, 2, 5, 1, 3]\n",
    "\n",
    "\n",
    "def smallest(a_list):\n",
    "    #YOUR CODE HERE\n",
    "    \n",
    "\n",
    "smallest(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Slicing**\n",
    "\n",
    "You can select sections of list-like types (arrays, tuples, NumPy arrays) by using slice\n",
    "notation, which in its basic form consists of start:stop passed to the indexing operator\n",
    "[]:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq = [7, 2, 3, 7, 5, 6, 0, 1]\n",
    "seq[1:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Slices can also be assigned to with a sequence:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq[3:4] = [6, 3]\n",
    "seq"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**While element at the start index is included, the stop index is not included**, so that\n",
    "the number of elements in the result is stop - start.\n",
    "\n",
    "Either the start or stop can be omitted in which case they default to the start of the\n",
    "sequence and the end of the sequence, respectively:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq[:5]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq[3:]\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Negative indices slice the sequence relative to the end:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq[-4:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq[-6:-2]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Slicing semantics takes a bit of getting used to, especially if you’re coming from R,\n",
    "MATLAB or any other programming language for that matter."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A step can also be used after a second colon to, say, take every other element:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq[::2]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A clever use of this is to pass -1 which has the useful effect of reversing a list or tuple:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq[::-1]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise: \n",
    "\n",
    "Write a function that takes a string and returns it with the effect of exchanging the first and last chars."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def change_string(str1):\n",
    "    #YOUR CODE HERE:\n",
    "    \n",
    "     \n",
    "\n",
    "print(change_string('abcd'))\n",
    "print(change_string('12345'))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Dict"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "dict is likely the most important built-in Python data structure. A more common name\n",
    "for it is hash map or associative array. It is a flexibly-sized collection of key-value pairs,\n",
    "where key and value are Python objects. One way to create one is by using curly braces\n",
    "{} and using colons to separate keys and values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "empty_dict = {}\n",
    "d1 = {'a' : 'some value', 'b' : [1, 2, 3, 4]}\n",
    "d1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Elements can be accessed and inserted or set using the same syntax as accessing elements\n",
    "of a list or tuple:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d1[7] = 'an integer'\n",
    "d1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d1['b']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise:\n",
    "\n",
    "Write a function to print a dictionary where the keys are numbers between 1 and 15 (both included) and the values are square of keys.\n",
    "\n",
    "Sample Dictionary\n",
    "{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100, 11: 121, 12: 144, 13: 169, 14: 196, 15: 225}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d = dict()\n",
    "#YOUR CODE HERE\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can check if a dict contains a key using the same syntax as with checking whether\n",
    "a list or tuple contains a value:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('b' in d1)\n",
    "print('some value' in d1.values())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Values can be deleted either using the del keyword or the pop method (which simultaneously\n",
    "returns the value and deletes the key):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d1[5] = 'some value'\n",
    "d1['dummy'] = 'another value'\n",
    "del d1[5]\n",
    "ret = d1.pop('dummy')\n",
    "ret"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The keys and values method give you lists of the keys and values, respectively."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d1.keys()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d1.values()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One dict can be merged into another using the update method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d1.update({'b' : 'foo', 'c' : 12})\n",
    "d1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It’s common to occasionally end up with two sequences that you want to pair up element-\n",
    "wise in a dict. As a first cut, you might write code like this:\n"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "mapping = {}\n",
    "for key, value in zip(key_list, value_list):\n",
    "    mapping[key] = value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since a dict is essentially a collection of 2-tuples, it should be no shock that the dict\n",
    "type function accepts a list of 2-tuples:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mapping = dict(zip(range(5), reversed(range(5))))\n",
    "mapping"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In a later section we’ll talk about dict comprehensions, another elegant way to construct\n",
    "dicts."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Default values\n",
    "It’s very common to have logic like:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "if key in some_dict:\n",
    "    value = some_dict[key]\n",
    "else:\n",
    "    value = default_value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Thus, the dict methods get and pop can take a default value to be returned, so that the\n",
    "above if-else block can be written simply as:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "value = some_dict.get(key, default_value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "get by default will return None if the key is not present, while pop will raise an exception.\n",
    "\n",
    "\n",
    "With setting values, a common case is for the values in a dict to be other collections,\n",
    "like lists. For example, you could imagine categorizing a list of words by their first letters\n",
    "as a dict of lists:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "words = ['apple', 'bat', 'bar', 'atom', 'book']\n",
    "by_letter = {}\n",
    "for word in words:\n",
    "    letter = word[0]\n",
    "    if letter not in by_letter:\n",
    "        by_letter[letter] = [word]\n",
    "    else:\n",
    "        by_letter[letter].append(word)\n",
    "by_letter"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Valid dict key types**\n",
    "\n",
    "While the values of a dict can be any Python object, the keys have to be immutable\n",
    "objects like scalar types (int, float, string) or tuples (all the objects in the tuple need to\n",
    "be immutable, too). The technical term here is hashability. You can check whether an\n",
    "object is hashable (can be used as a key in a dict) with the hash function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hash('string')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hash((1, 2, (2, 3)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hash((1, 2, [2, 3])) # fails because lists are mutable"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To use a list as a key, an easy fix is to convert it to a tuple:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d = {}\n",
    "d[tuple([1, 2, 3])] = 5\n",
    "d"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Set**\n",
    "\n",
    "A set is an unordered collection of unique elements. You can think of them like dicts,\n",
    "but keys only, no values. A set can be created in two ways: via the set function or using\n",
    "a set literal with curly braces:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "set([2, 2, 2, 1, 3, 3])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "{2, 2, 2, 1, 3, 3}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise:\n",
    "\n",
    "Given a list having the following values [2,4,5,3,3,3,5,6,7,3,6,5,7], extract only the unique elements:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**List, Set, and Dict Comprehensions**\n",
    "\n",
    "List comprehensions are one of the most-loved Python language features. \n",
    "\n",
    "**They allow\n",
    "you to concisely form a new list by filtering the elements of a collection and transforming\n",
    "the elements passing the filter in one conscise expression.**\n",
    "\n",
    "They take the basic form:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "[expr for val in collection if condition]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is equivalent to the following for loop:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "result = []\n",
    "for val in collection:\n",
    "      if condition:\n",
    "          result.append(expr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The filter condition can be omitted, leaving only the expression. For example, given a\n",
    "list of strings, we could filter out strings with length 2 or less and also convert them to\n",
    "uppercase like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "strings = ['a', 'as', 'bat', 'car', 'dove', 'python']\n",
    "[x.upper() for x in strings if len(x) > 2]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Set and dict comprehensions are a natural extension, producing sets and dicts in a\n",
    "idiomatically similar way instead of lists. \n",
    "\n",
    "A dict comprehension looks like this:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "dict_comp = {key-expr : value-expr for value in collection if condition}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A set comprehension looks like the equivalent list comprehension except with curly\n",
    "braces instead of square brackets:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "set_comp = {expr for value in collection if condition}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Like list comprehensions, set and dict comprehensions are just syntactic sugar, but they\n",
    "similarly can make code both easier to write and read. \n",
    "\n",
    "Consider the list of strings below. Suppose we wanted a set containing just the lengths of the strings contained in the collection; this could be easily computed using a set comprehension:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "strings = ['a', 'as', 'bat', 'car', 'dove', 'python']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "unique_lengths = {len(x) for x in strings}\n",
    "unique_lengths"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As a simple dict comprehension example, we could create a lookup map of these strings\n",
    "to their locations in the list:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "loc_mapping = {val : index for index, val in enumerate(strings)}\n",
    "loc_mapping"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "list(enumerate(strings))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that this dict could be equivalently constructed by:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "loc_mapping = dict((val, idx) for idx, val in enumerate(strings))\n",
    "loc_mapping"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### More on IPython, Notebook and Tips for Productive Code Development"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Writing code in a way that makes it easy to develop, debug, and ultimately use interactively\n",
    "may be a paradigm shift for many users. There are procedural details like code\n",
    "reloading that may require some adjustment as well as coding style concerns.\n",
    "\n",
    "As such, most of this section is more of an art than a science and will require some\n",
    "experimentation on your part to determine a way to write your Python code that is\n",
    "effective and productive for you. Ultimately you want to structure your code in a way\n",
    "that makes it easy to use iteratively and to be able to explore the results of running a\n",
    "program or function as effortlessly as possible. I have found software designed with\n",
    "IPython in mind to be easier to work with than code intended only to be run as as\n",
    "standalone command-line application. This becomes especially important when something\n",
    "goes wrong and you have to diagnose an error in code that you or someone else\n",
    "might have written months or years beforehand."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Reloading Module Dependencies"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In Python, when you type import some_lib, the code in some_lib is executed and all the\n",
    "variables, functions, and imports defined within are stored in the newly created\n",
    "some_lib module namespace. The next time you type import some_lib, you will get a\n",
    "reference to the existing module namespace. The potential difficulty in interactive code\n",
    "development in IPython comes when you, say, %run a script that depends on some other\n",
    "module where you may have made changes. Suppose I had the following code in\n",
    "test_script.py:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "import some_lib\n",
    "\n",
    "x = 5\n",
    "y = [1, 2, 3, 4]\n",
    "result = some_lib.get_answer(x, y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you were to execute %run test_script.py then modify some_lib.py, the next time you\n",
    "execute %run test_script.py you will still get the old version of some_lib because of\n",
    "Python’s “load-once” module system. This behavior differs from some other data analysis\n",
    "environments, like MATLAB, which automatically propagate code changes.1 To\n",
    "cope with this, you have a couple of options. The first way is to use Python's built-in\n",
    "reload function, altering test_script.py to look like the following:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "import some_lib\n",
    "from importlib import reload\n",
    "reload(some_lib)\n",
    "\n",
    "x = 5\n",
    "y = [1, 2, 3, 4]\n",
    "result = some_lib.get_answer(x, y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This guarantees that you will get a fresh copy of some_lib every time you run\n",
    "test_script.py. Obviously, if the dependencies go deeper, it might be a bit tricky to be\n",
    "inserting usages of reload all over the place. For this problem, IPython has a special\n",
    "dreload function (not a magic function) for “deep” (recursive) reloading of modules. If\n",
    "I were to run import some_lib then type dreload(some_lib), it will attempt to reload\n",
    "some_lib as well as all of its dependencies. This will not work in all cases, unfortunately,\n",
    "but when it does it beats having to restart IPython.\n",
    "\n",
    "An even better solution is to use a Notebook extension as follows which will automatically reload all imports of your modified libraries:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%load_ext autoreload\n",
    "%autoreload 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Keep relevant objects and data alive**\n",
    "\n",
    "It’s not unusual to see a program written for the command line with a structure somewhat\n",
    "like the following trivial example:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "from my_functions import g\n",
    "\n",
    "def f(x, y):\n",
    "    return g(x + y)\n",
    "\n",
    "def main():\n",
    "    x = 6\n",
    "    y = 7.5\n",
    "    result = x + y\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Do you see what might be wrong with this program if we were to run it in IPython?\n",
    "After it’s done, none of the results or objects defined in the main function willl be accessible\n",
    "in the IPython shell. A better way is to have whatever code is in main execute\n",
    "directly in the module’s global namespace (or in the if __name__ == '__main__': block,\n",
    "if you want the module to also be importable). That way, when you %run the code,\n",
    "you’ll be able to look at all of the variables defined in main. It’s less meaningful in this\n",
    "simple example, but in this book we’ll be looking at some complex data analysis problems\n",
    "involving large data sets that you will want to be able to play with in IPython.\n",
    "\n",
    "**Flat is better than nested**\n",
    "\n",
    "Deeply nested code makes me think about the many layers of an onion. When testing\n",
    "or debugging a function, how many layers of the onion must you peel back in order to\n",
    "reach the code of interest? The idea that “flat is better than nested” is a part of the Zen\n",
    "of Python, and it applies generally to developing code for interactive use as well. Making\n",
    "functions and classes as decoupled and modular as possible makes them easier to test\n",
    "(if you are writing unit tests), debug, and use interactively.\n",
    "\n",
    "**Overcome a fear of longer files**\n",
    "\n",
    "If you come from a Java (or another such language) background, you may have been\n",
    "told to keep files short. In many languages, this is sound advice; long length is usually\n",
    "a bad “code smell”, indicating refactoring or reorganization may be necessary. However,\n",
    "while developing code using IPython, working with 10 small, but interconnected\n",
    "files (under, say, 100 lines each) is likely to cause you more headache in general than a\n",
    "single large file or two or three longer files. Fewer files means fewer modules to reload\n",
    "and less jumping between files while editing, too. I have found maintaining larger\n",
    "modules, each with high internal cohesion, to be much more useful and pythonic. After\n",
    "iterating toward a solution, it sometimes will make sense to refactor larger files into\n",
    "smaller ones.\n",
    "\n",
    "Obviously, I don’t support taking this argument to the extreme, which would to be to\n",
    "put all of your code in a single monstrous file. Finding a sensible and intuitive module\n",
    "and package structure for a large codebase often takes a bit of work, but it is especially\n",
    "important to get right in teams. Each module should be internally cohesive, and it\n",
    "should be as obvious as possible where to find functions and classes responsible for\n",
    "each area of functionality."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Profiles and Configuration**\n",
    "\n",
    "Most aspects of the appearance (colors, prompt, spacing between lines, etc.) and behavior\n",
    "of the IPython shell are configurable through an extensive configuration system.\n",
    "Here are some of the things you can do via configuration:\n",
    "\n",
    "• Change the color scheme\n",
    "\n",
    "• Change how the input and output prompts look, or remove the blank line after\n",
    "Out and before the next In prompt\n",
    "\n",
    "• Change how the input and output prompts look\n",
    "\n",
    "• Execute an arbitrary list of Python statements. These could be imports that you\n",
    "use all the time or anything else you want to happen each time you launch IPython\n",
    "\n",
    "• Enable IPython extensions, like the %lprun magic in line_profiler\n",
    "\n",
    "• Define your own magics or system aliases\n",
    "\n",
    "All of these configuration options are specified in a special ipython_config.py file which\n",
    "will be found in the ~/.config/ipython/ directory on UNIX-like systems and %HOME\n",
    "%/.ipython/ directory on Windows. Where your home directory is depends on your\n",
    "system. Configuration is performed based on a particular profile. When you start IPython\n",
    "normally, you load up, by default, the default profile, stored in the pro\n",
    "file_default directory. Thus, on my Windows the full path to my default IPython\n",
    "configuration file is:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "C:\\\\Users\\\\tsusnjak\\\\.ipython\\\\profile_default\\\\ipython_config.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "I’ll spare you the gory details of what’s in this file. Fortunately it has comments describing\n",
    "what each configuration option is for, so I will leave it to the reader to tinker\n",
    "and customize. One additional useful feature is that it’s possible to have multiple profiles.\n",
    "Suppose you wanted to have an alternate IPython configuration tailored for a\n",
    "particular application or project. Creating a new profile is as simple is typing something\n",
    "like"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "ipython profile create secret_project"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once you’ve done this, edit the config files in the newly-created pro\n",
    "file_secret_project directory then launch IPython like so"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "$ ipython --profile=secret_project\n",
    "Python 2.7.2 |EPD 7.1-2 (64-bit)| (default, Jul 3 2011, 15:17:51)\n",
    "Type \"copyright\", \"credits\" or \"license\" for more information.\n",
    "\n",
    "IPython 0.13 -- An enhanced Interactive Python.\n",
    "?           -> Introduction and overview of IPython's features.\n",
    "%quickref   -> Quick reference.\n",
    "help        -> Python's own help system.\n",
    "object?     -> Details about 'object', use 'object??' for extra details.\n",
    "\n",
    "IPython profile: secret_project\n",
    "\n",
    "In [1]:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Tab Completion"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On the surface, the IPython shell looks like a cosmetically slightly-different interactive\n",
    "Python interpreter. Users of Mathematica may find the enumerated input and output\n",
    "prompts familiar. One of the major improvements over the standard Python shell is\n",
    "tab completion, a feature common to most interactive data analysis environments.\n",
    "While entering expressions in the shell, pressing <Tab> will search the namespace for\n",
    "any variables (objects, functions, etc.) matching the characters you have typed so far."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that IPython by default hides methods and attributes starting with\n",
    "underscores, such as magic methods and internal “private” methods\n",
    "and attributes, in order to avoid cluttering the display (and confusing\n",
    "new Python users!). These, too, can be tab-completed but you must first\n",
    "type an underscore to see them. If you prefer to always see such methods\n",
    "in tab completion, you can change this setting in the IPython configuration."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Introspection**\n",
    "\n",
    "Using a question mark (?) before or after a variable will display some general information\n",
    "about the object:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "In [545]: b?\n",
    "Type: list\n",
    "String Form:[1, 2, 3]\n",
    "Length: 3\n",
    "Docstring:\n",
    "list() -> new empty list\n",
    "list(iterable) -> new list initialized from iterable's items"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is referred to as object introspection. If the object is a function or instance method,\n",
    "the docstring, if defined, will also be shown."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using ?? will also show the function’s source code if possible."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "? has a final usage, which is for searching the IPython namespace in a manner similar\n",
    "to the standard UNIX or Windows command line. A number of characters combined\n",
    "with the wildcard (*) will show all names matching the wildcard expression. For example,\n",
    "we could get a list of all functions in the top level NumPy namespace containing\n",
    "load:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "In [549]: np.*load*?\n",
    "np.load\n",
    "np.loads\n",
    "np.loadtxt\n",
    "np.pkgload"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**The %run Command**\n",
    "\n",
    "Any file can be run as a Python program inside the environment of your IPython session\n",
    "using the %run command. Suppose you had the following simple script stored in ipy\n",
    "thon_script_test.py:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def f(x, y, z):\n",
    "    return (x + y) / z\n",
    "\n",
    "a = 5\n",
    "b = 6\n",
    "c = 7.5\n",
    "result = f(a, b, c)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This can be executed by passing the file name to %run:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "%run ipython_script_test.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The script is run in an empty namespace (with no imports or other variables defined)\n",
    "so that the behavior should be identical to running the program on the command line\n",
    "using python script.py. All of the variables (imports, functions, and globals) defined\n",
    "in the file (up until an exception, if any, is raised) will then be accessible in the IPython\n",
    "shell:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "In [551]: c\n",
    "Out[551]: 7.5\n",
    "\n",
    "In [552]: result\n",
    "Out[552]: 1.4666666666666666"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If a Python script expects command line arguments (to be found in sys.argv), these\n",
    "can be passed after the file path as though run on the command line.\n",
    "\n",
    "Should you wish to give a script access to variables already defined in\n",
    "the interactive IPython namespace, use %run -i instead of plain %run."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Interrupting running code**\n",
    "\n",
    "Pressing <Ctrl-C> while any code is running, whether a script through %run or a longrunning\n",
    "command, will cause a KeyboardInterrupt to be raised. This will cause nearly\n",
    "all Python programs to stop immediately except in very exceptional cases."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Executing Code from the Clipboard**\n",
    "\n",
    "A quick-and-dirty way to execute code in IPython is via pasting from the clipboard.\n",
    "This might seem fairly crude, but in practice it is very useful. For example, while developing\n",
    "a complex or time-consuming application, you may wish to execute a script\n",
    "piece by piece, pausing at each stage to examine the currently loaded data and results.\n",
    "Or, you might find a code snippet on the Internet that you want to run and play around\n",
    "with, but you’d rather not create a new .py file for it.\n",
    "\n",
    "Code snippets can be pasted from the clipboard in many cases by pressing <Ctrl-Shift-\n",
    "V>. Note that it is not completely robust as this mode of pasting mimics typing each\n",
    "line into IPython, and line breaks are treated as <return>. This means that if you paste\n",
    "code with an indented block and there is a blank line, IPython will think that the indented\n",
    "block is over. Once the next line in the block is executed, an IndentationEr\n",
    "ror will be raised. To get around this use %paste"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Magic Commands**\n",
    "\n",
    "IPython has many special commands, known as “magic” commands, which are designed\n",
    "to faciliate common tasks and enable you to easily control the behavior of the\n",
    "IPython system. A magic command is any command prefixed by the the percent symbol\n",
    "%. For example, you can check the execution time of any Python statement, such as a\n",
    "matrix multiplication, using the %timeit magic function (which will be discussed in\n",
    "more detail later):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "a = np.random.randn(100, 100)\n",
    "%timeit np.dot(a, a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Magic commands can be viewed as command line programs to be run within the IPython\n",
    "system. Many of them have additional “command line” options, which can all be\n",
    "viewed (as you might expect) using ?:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%reset?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Magic functions can be used by default without the percent sign, as long as no variable\n",
    "is defined with the same name as the magic function in question. This feature is called\n",
    "automagic and can be enabled or disabled using %automagic.\n",
    "Since IPython’s documentation is easily accessible from within the system, I encourage\n",
    "you to explore all of the special commands available by typing %quickref or %magic. I\n",
    "will highlight a few more of the most critical ones for being productive in interactive\n",
    "computing and Python development in IPython."
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "Command        Description\n",
    "%quickref      Display the IPython Quick Reference Card\n",
    "%magic         Display detailed documentation for all of the available magic commands\n",
    "%debug         Enter the interactive debugger at the bottom of the last exception traceback\n",
    "%hist          Print command input (and optionally output) history\n",
    "%pdb           Automatically enter debugger after any exception\n",
    "%paste         Execute pre-formatted Python code from clipboard\n",
    "%cpaste        Open a special prompt for manually pasting Python code to be executed\n",
    "%reset         Delete all variables / names defined in interactive namespace\n",
    "%page          OBJECT Pretty print the object and display it through a pager\n",
    "%run script.py Run a Python script inside IPython\n",
    "%prun statement Execute statement with cProfile and report the profiler output\n",
    "%time statement Report the execution time of single statement\n",
    "%timeit statement Run a statement multiple times to compute an emsemble average execution time. Useful for\n",
    "timing code with very short execution time\n",
    "%who, %who_ls, %whos Display variables defined in interactive namespace, with varying levels of information / verbosity\n",
    "%xdel variable Delete a variable and attempt to clear any references to the object in the IPython internals"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Interacting with the Operating System**\n",
    "\n",
    "Another important feature of IPython is that it provides very strong integration with\n",
    "the operating system shell. This means, among other things, that you can perform most\n",
    "standard command line actions as you would in the Windows or UNIX (Linux, OS X)\n",
    "shell without having to exit IPython. This includes executing shell commands, changing\n",
    "directories, and storing the results of a command in a Python object (list or string).\n",
    "There are also simple shell command aliasing and directory bookmarking features.\n",
    "See Table 3-3 for a summary of magic functions and syntax for calling shell commands.\n",
    "I’ll briefly visit these features in the next few sections."
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "Command                  Description\n",
    "!cmd                     Execute cmd in the system shell\n",
    "output = !cmd args       Run cmd and store the stdout in output\n",
    "%alias alias_name cmd    Define an alias for a system (shell) command\n",
    "%bookmark                Utilize IPython’s directory bookmarking system\n",
    "%cd                      directory Change system working directory to passed directory\n",
    "%pwd                     Return the current system working directory\n",
    "%pushd directory         Place current directory on stack and change to target directory\n",
    "%popd                    Change to directory popped off the top of the stack\n",
    "%dirs                    Return a list containing the current directory stack\n",
    "%dhist                   Print the history of visited directories\n",
    "%env                     Return the system environment variables as a dict"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Exercises"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Write a function that takes a string and prints out whether this string is a palindrome or not. (A palindrome is a string that reads the same forwards and backwards.)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Write a function that takes a list and returns a new list that contains all the elements of the first list minus all the duplicates. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Write a function for calculating Fibonnaci numbers. The function accepts a number representing the sequence size.(Hint: The Fibonnaci seqence is a sequence of numbers where the next number in the sequence is the sum of the previous two numbers in the sequence. The sequence looks like this: 1, 1, 2, 3, 5, 8, 13, …)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.1"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
